﻿// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace umbraco.ACSExtensions
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net.Configuration;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Web.Configuration;
    using Microsoft.IdentityModel.Claims;
    using Microsoft.Samples.DPE.ACS.ServiceManagementWrapper;
    using umbraco.BusinessLogic;
    using umbraco.cms.businesslogic.datatype;
    using umbraco.cms.businesslogic.macro;
    using umbraco.cms.businesslogic.member;
    using umbraco.cms.businesslogic.propertytype;
    using umbraco.IO;

    public partial class ACSExtensions : System.Web.UI.UserControl
    {
        private const string WindowsLiveIdIdentityProvider = "Windows Live ID";
        private const string GoogleIdentityProvider = "Google";
        private const string YahooIdentityProvider = "Yahoo!";
        private const string FacebookIdentityProvider = "Facebook";

        private static string[] socialIdentityProviders = new string[] { WindowsLiveIdIdentityProvider, GoogleIdentityProvider, YahooIdentityProvider, FacebookIdentityProvider };

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                var configuration = WebConfigurationManager.OpenWebConfiguration(this.Request.ApplicationPath);

                // Default values - ACS
                if (configuration.AppSettings.Settings.AllKeys.Any(k => k == "AcsNamespace"))
                {
                    this.AcsNamespace.Text = configuration.AppSettings.Settings["AcsNamespace"].Value;
                }

                if (configuration.AppSettings.Settings.AllKeys.Any(k => k == "AcsManagementKey"))
                {
                    this.AcsManagementKey.Text = configuration.AppSettings.Settings["AcsManagementKey"].Value;
                }

                this.AcsRealmAddress.Text = new Uri(ACSExtensionsSettings.GetAppDomainAddress()).AbsoluteUri;

                // Default values - SMTP
                var mailSettings = configuration.GetSectionGroup("system.net/mailSettings") as MailSettingsSectionGroup;

                if (mailSettings != null)
                {
                    this.SmtpServerName.Text = mailSettings.Smtp.Network.Host;
                    this.SmtpPort.Text = mailSettings.Smtp.Network.Port.ToString();
                    this.SmtpUser.Text = mailSettings.Smtp.Network.UserName;
                    this.SmtpPassword.Attributes["value"] = mailSettings.Smtp.Network.Password;
                }
            }
        }

        protected void SaveFederationConfiguration(object sender, EventArgs e)
        {
            try
            {
                // 0. Backup web.config
                BackupFile(WebConfigurationManager.OpenWebConfiguration(this.Request.ApplicationPath).FilePath);
                
                // 1. Register login macros
                RegisterLoginMacro(ACSExtensionsSettings.FederatedLoginMacroAlias, "FederatedLogin.ascx");
                RegisterLoginMacro(ACSExtensionsSettings.LoginMacroAlias, "Login.ascx");

                // 2. Create Federated Member type (and generic properties)
                CreateMemberType();

                // 3. Configure ACS namespace
                var relyingPartyName = string.Format(CultureInfo.InvariantCulture, "Umbraco Relying Party ({0})", this.AcsRealmAddress.Text);

                if (!string.IsNullOrWhiteSpace(this.AcsManagementKey.Text))
                {
                    var allowedIdentityProviders = new List<string>();
                    for (var i = 0; i < this.AcsSocialIdentityProviders.Items.Count; i++)
                    {
                        if (this.AcsSocialIdentityProviders.Items[i].Selected)
                        {
                            allowedIdentityProviders.Add(this.AcsSocialIdentityProviders.Items[i].Value);
                        }
                    }

                    ConfigureAccessControlNamespace(this.AcsNamespace.Text, this.AcsManagementKey.Text, relyingPartyName, this.AcsRealmAddress.Text, allowedIdentityProviders, this.FacebookAppId.Text, this.FacebookAppSecret.Text);
                }

                // 4. Configure WIF
                var realm = new Uri(this.AcsRealmAddress.Text);
                var issuer = new Uri(string.Format(CultureInfo.InvariantCulture, "https://{0}.accesscontrol.windows.net/", this.AcsNamespace.Text));
                var trustedIssuerThumbprint = "{trustedIssuerThumbprint}";

                if (!string.IsNullOrWhiteSpace(this.AcsManagementKey.Text))
                {
                    var wrapper = new ServiceManagementWrapper(this.AcsNamespace.Text, "ManagementClient", this.AcsManagementKey.Text);

                    var signingKey = wrapper.RetrieveServiceKeys().SingleOrDefault(sk => sk.Type == KeyType.X509Certificate.ToString() && sk.Usage == KeyUsage.Signing.ToString() && sk.IsPrimary);
                    if (signingKey != null)
                    {
                        trustedIssuerThumbprint = new X509Certificate2(signingKey.Value, new ASCIIEncoding().GetString(signingKey.Password)).Thumbprint;
                    }
                }

                this.ConfigureFederation(realm, issuer, trustedIssuerThumbprint);

                // 5. Configure SMTP
                this.ConfigureSmtp(this.SmtpServerName.Text, int.Parse(this.SmtpPort.Text, CultureInfo.InvariantCulture), this.SmtpUser.Text, this.SmtpPassword.Text);

                // 6. Add appSettings keys
                this.AddAppSettings();

                // 7. Add ApplicationTree node for Members -> Business Identity Providers
                var treeAlias = "businessIdentityProviders";
                if (ApplicationTree.getByAlias(treeAlias) == null)
                {
                    ApplicationTree.MakeNew(false, true, 3, "member", treeAlias, "Business Identity Providers", ".sprTreeFolder", ".sprTreeFolder_o", "umbraco", "loadBusinessIdentityProviders", string.Empty);
                }
            }
            catch (Exception)
            {
                throw;
            }

            this.Settings.Visible = false;
            this.Installing.Visible = true;
        }
        
        protected void GotoNextStep(object sender, EventArgs e)
        {
            // TODO: Improve this
            var nextStepAlias = "skinning";
            this.Response.Redirect("?installStep=" + nextStepAlias);
        }

        private static void BackupFile(string fileName)
        {
            string path = string.Format(CultureInfo.InvariantCulture, "{0}.backup.acsExtensions", fileName);
            if (!File.Exists(path))
            {
                File.Copy(fileName, path, false);
            }
        }

        private static void RegisterLoginMacro(string macroAlias, string macroFileName)
        {
            var macro = Macro.GetByAlias(macroAlias);
            if (macro == null)
            {
                string rootDir = IOHelper.MapPath(SystemDirectories.Root);
                string userControlsPath = IOHelper.MapPath(SystemDirectories.Usercontrols);
                var macroPath = Path.Combine(userControlsPath, macroFileName);

                macro = Macro.MakeNew(macroAlias);
                macro.UseInEditor = true;
                macro.RenderContent = false;
                macro.Type = new FileInfo(macroPath).FullName.Substring(rootDir.Length).Replace(IOHelper.DirSepChar, '/');
                macro.Save();
            }
        }

        private static void CreateMemberType()
        {
            var memberType = MemberType.GetByAlias(ACSExtensionsSettings.SocialMemberTypeAlias);
            if (memberType == null)
            {
                memberType = MemberType.MakeNew(User.GetUser(0), ACSExtensionsSettings.SocialMemberTypeAlias);
                memberType.Text = ACSExtensionsSettings.SocialMemberTypeText;
                memberType.Save();

                // Generic properties
                var labelDataTypeDefinition = DataTypeDefinition.GetAll().Single(dtd => dtd.Text == "Label");
                PropertyType.MakeNew(labelDataTypeDefinition, memberType, ACSExtensionsSettings.MemberIdentityProviderPropertyName, ACSExtensionsSettings.MemberIdentityProviderPropertyAlias);
                PropertyType.MakeNew(labelDataTypeDefinition, memberType, ACSExtensionsSettings.MemberInvitationSentPropertyName, ACSExtensionsSettings.MemberInvitationSentPropertyAlias);
                PropertyType.MakeNew(labelDataTypeDefinition, memberType, ACSExtensionsSettings.MemberIsActivatedPropertyName, ACSExtensionsSettings.MemberIsActivatedPropertyAlias);
            }
        }

        private static void ConfigureAccessControlNamespace(string acsNamespace, string acsManagementKey, string relyingPartyName, string realmAddress, List<string> allowedIdentityProviders, string facebookAppId = "", string facebookAppSecret = "")
        {
            var wrapper = new ServiceManagementWrapper(acsNamespace, "ManagementClient", acsManagementKey);
            
            // 1. Add Social Identity Providers (Yahoo and Google, LiveID is always configured)
            if (allowedIdentityProviders.Contains(YahooIdentityProvider))
            {
                wrapper.RemoveIdentityProvider(YahooIdentityProvider);
                wrapper.AddIdentityProviderManually(YahooIdentityProvider, "https://open.login.yahooapis.com/openid/op/auth", WebSSOProtocolType.OpenId);
            }

            if (allowedIdentityProviders.Contains(GoogleIdentityProvider))
            {
                wrapper.RemoveIdentityProvider(GoogleIdentityProvider);
                wrapper.AddIdentityProviderManually(GoogleIdentityProvider, "https://www.google.com/accounts/o8/ud", WebSSOProtocolType.OpenId);
            }

            if (!string.IsNullOrWhiteSpace(facebookAppId) && !string.IsNullOrWhiteSpace(facebookAppSecret))
            {
                wrapper.RemoveIdentityProvider(FacebookIdentityProvider);
                wrapper.AddFacebookIdentityProvider(FacebookIdentityProvider, facebookAppId, facebookAppSecret);
                allowedIdentityProviders.Add(FacebookIdentityProvider);
            }

            // 2. Create Relying Party
            var relyingPartyRealmAddress = realmAddress;
            var relyingPartyReplyAddress = realmAddress;
            var ruleGroupName = "Default Rule Group for " + relyingPartyName;

            var relyingParty = wrapper.RetrieveRelyingParty(relyingPartyName);
            if (relyingParty != null)
            {
                foreach (var identityProvider in relyingParty.RelyingPartyIdentityProviders)
                {
                    if (!socialIdentityProviders.Contains(identityProvider.IdentityProvider.DisplayName))
                    {
                        allowedIdentityProviders.Add(identityProvider.IdentityProvider.DisplayName);
                    }
                    else
                    {
                        wrapper.RemoveAllRulesInGroup(ruleGroupName, identityProvider.IdentityProvider.DisplayName);
                    }
                }
            }

            wrapper.RemoveRelyingParty(relyingPartyName);
            wrapper.AddRelyingParty(
                    relyingPartyName,
                    relyingPartyRealmAddress,
                    relyingPartyReplyAddress,
                    ruleGroupName: ruleGroupName,
                    allowedIdentityProviders: allowedIdentityProviders.ToArray());

            // 3. Create rules
            if (allowedIdentityProviders.Contains(WindowsLiveIdIdentityProvider))
            {
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, WindowsLiveIdIdentityProvider, ClaimTypes.NameIdentifier, ClaimTypes.Name);
            }

            if (allowedIdentityProviders.Contains(YahooIdentityProvider))
            {
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, YahooIdentityProvider, ClaimTypes.Email);
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, YahooIdentityProvider, ClaimTypes.NameIdentifier, ClaimTypes.Name);
            }

            if (allowedIdentityProviders.Contains(GoogleIdentityProvider))
            {
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, GoogleIdentityProvider, ClaimTypes.Email);
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, GoogleIdentityProvider, ClaimTypes.NameIdentifier, ClaimTypes.Name);
            }

            if (allowedIdentityProviders.Contains(FacebookIdentityProvider))
            {
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, FacebookIdentityProvider, ClaimTypes.Email);
                wrapper.AddPassthroughRuleToRuleGroup(ruleGroupName, FacebookIdentityProvider, ClaimTypes.NameIdentifier, ClaimTypes.Name);
            }
        }

        private void ConfigureFederation(Uri realm, Uri issuer, string trustedIssuerThumbprint)
        {
            var configurationPath = WebConfigurationManager.OpenWebConfiguration(this.Request.ApplicationPath).FilePath;
            var configManager = new FederationConfiguationManager(configurationPath);

            // configSections
            if (configManager.ReadIdfxConfigurationSectionNode() == null)
            {
                configManager.WriteIdfxConfigurationSectionNode();
            }

            // httpRuntime
            var requestValidationType = typeof(WSFederationRequestValidator).AssemblyQualifiedName;
            if (configManager.ReadHttpRuntime() == null ||
                configManager.ReadHttpRuntime().Attributes["requestValidationType"] == null ||
                (configManager.ReadHttpRuntime().Attributes["requestValidationType"] != null &&
                 configManager.ReadHttpRuntime().Attributes["requestValidationType"].Value != requestValidationType))
            {
                configManager.WriteHttpRuntimeRequestValidationMode(requestValidationType);
            }

            // CustomWSFederationAuthenticationModule
            var customWSFederationAuthenticationModuleName = typeof(CustomWSFederationAuthenticationModule).Name;
            if (configManager.ReadWebServerWSFederationAuthenticationModuleNode(customWSFederationAuthenticationModuleName) == null)
            {
                var customWSFederationAuthenticationModuleType = typeof(CustomWSFederationAuthenticationModule).FullName;
                configManager.WriteWebServerWSFederationAuthenticationModuleNode(customWSFederationAuthenticationModuleName, customWSFederationAuthenticationModuleType);
            }

            // SessionAuthenticationModule
            if (configManager.ReadWebServerSessionAuthenticationModuleNode() == null)
            {
                configManager.WriteWebServerSessionAuthenticationModuleNode();
            }

            // FederatedMembersInvitationModule
            var federatedMembersInvitationModuleName = typeof(FederatedMembersInvitationModule).Name;
            if (configManager.ReadWebServerCustomModuleNode(federatedMembersInvitationModuleName) == null)
            {
                var federatedMembersInvitationModuleType = typeof(FederatedMembersInvitationModule).FullName;
                configManager.WriteWebServerCustomModuleNode(federatedMembersInvitationModuleName, federatedMembersInvitationModuleType);
            }

            // location
            var locationPath = "umbraco";
            if (configManager.ReadLocationAuthorization(locationPath) == null)
            {
                configManager.WriteLocationAuthorization(locationPath);
            }

            // microsoft.identityModel
            string serviceName = null;

            // microsoft.identityModel / audienceUris
            var audienceUris = configManager.ReadAudienceUris(serviceName);
            if (audienceUris != null)
            {
                if (!audienceUris.Contains(realm))
                {
                    configManager.RemoveNode("/fed:configuration/fed:microsoft.identityModel/fed:service[count(@name)=0 or @name='']/fed:audienceUris");
                    audienceUris.Add(realm);
                    configManager.WriteAudienceUri(audienceUris, serviceName);
                }
            }
            else
            {
                configManager.WriteAudienceUri(realm, serviceName);
            }

            // microsoft.identityModel / federatedAuthentication
            var federatedAuthenticationElement = configManager.ReadFederatedAuthentication();

            if (federatedAuthenticationElement == null)
            {
                federatedAuthenticationElement = new FederatedAuthenticationElement();
            }
            else
            {
                configManager.RemoveNode("/fed:configuration/fed:microsoft.identityModel/fed:service[count(@name)=0 or @name='']/fed:federatedAuthentication");
            }

            federatedAuthenticationElement.WSFederation = new WSFederationElement(issuer.AbsoluteUri, realm.AbsoluteUri, false);
            federatedAuthenticationElement.WSFederation.RequireHttps =
                StringComparer.Ordinal.Equals(issuer.Scheme, Uri.UriSchemeHttps) &&
                StringComparer.Ordinal.Equals(realm.Scheme, Uri.UriSchemeHttps);

            configManager.UpdateCookieHandlerRequireSsl(federatedAuthenticationElement, federatedAuthenticationElement.WSFederation.RequireHttps);
            configManager.WriteFederatedAuthentication(federatedAuthenticationElement);

            // microsoft.identityModel / certificateValidation
            configManager.WriteDisabledCertificateValidation(serviceName);

            // microsoft.identityModel / issuerNameRegistry
            var trustedIssuerName = issuer.AbsoluteUri;
            configManager.UpdateIssuerNameRegistryElement(serviceName, new Collection<string> { trustedIssuerThumbprint }, trustedIssuerName);

            // microsoft.identityModel / claimsAuthenticationManager
            var claimsAuthenticationManagerType = typeof(CustomClaimsAuthenticationManager).FullName;
            configManager.WriteClaimsAuthenticationManager(claimsAuthenticationManagerType, serviceName);

            // Save web.config
            configManager.FlushDocument();
        }

        private void ConfigureSmtp(string host, int port, string userName, string userPassword)
        {
            var configuration = WebConfigurationManager.OpenWebConfiguration(this.Request.ApplicationPath);
            var mailSettings = configuration.GetSectionGroup("system.net/mailSettings") as MailSettingsSectionGroup;

            if (mailSettings == null)
            {
                mailSettings = new MailSettingsSectionGroup();
                configuration.SectionGroups.Add("system.net/mailSettings", mailSettings);
            }

            mailSettings.Smtp.Network.Host = host;
            mailSettings.Smtp.Network.Port = port;
            mailSettings.Smtp.Network.UserName = userName;
            mailSettings.Smtp.Network.Password = userPassword;

            configuration.Save();
        }

        private void AddAppSettings()
        {
            var configuration = WebConfigurationManager.OpenWebConfiguration(this.Request.ApplicationPath);

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "ActivationPage"))
            {
                configuration.AppSettings.Settings.Add("ActivationPage", "~/activation");
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "ActivationSenderAddress"))
            {
                configuration.AppSettings.Settings.Add("ActivationSenderAddress", "no-reply@domain.com");
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "ActivationSenderName"))
            {
                configuration.AppSettings.Settings.Add("ActivationSenderName", string.Empty);
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "ActivationEmailSubject"))
            {
                configuration.AppSettings.Settings.Add("ActivationEmailSubject", "Please Activate your Account");
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "ActivationEmailTemplate"))
            {
                configuration.AppSettings.Settings.Add("ActivationEmailTemplate", ACSExtensionsSettings.ActivationEmailTemplatePath);
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "AcsNamespace"))
            {
                configuration.AppSettings.Settings.Add("AcsNamespace", this.AcsNamespace.Text);
            }
            else
            {
                configuration.AppSettings.Settings["AcsNamespace"].Value = this.AcsNamespace.Text;
            }

            if (!configuration.AppSettings.Settings.AllKeys.Any(k => k == "AcsManagementKey"))
            {
                configuration.AppSettings.Settings.Add("AcsManagementKey", this.AcsManagementKey.Text);
            }
            else
            {
                configuration.AppSettings.Settings["AcsManagementKey"].Value = this.AcsManagementKey.Text;
            }

            configuration.Save();
        }
    }
}